﻿using Fleck;
using FySystem.Extension;
using FySystem.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;

namespace FyAutoStartService
{
    /// <summary>
    /// 通用远程升级服务端，基于WSocketListenerHelper和FileTransfer，只适用于比较小的程序进行更新，因为每次更新都会下载程序的所有文件。
    /// 它有一个基础路径，如：e:\\upgrades\，在构造函数中通过basePath传入，在该路径下的每一个文件夹都表示一个程序，如：
    /// e:\\upgrades\笔记本\               --该文件夹中存放的是“笔记本”这个程序的升级文件，客户端在检测是否有升级文件的时候，上传的AppName应和文件夹名称一致：笔记本
    /// e:\\upgrades\图片浏览器\           --该文件夹中存放的是“图片浏览器”这个程序的升级文件，客户端在检测是否有升级文件的时候，上传的AppName应为：图片浏览器
    /// 每个程序的文件夹内只存放一个最新版本的所有文件，更新的时候客户端将该文件夹内所有文件下载到本地，程序文件夹内也应有一个文件夹，文件夹名是版本号，如：
    /// e:\\upgrades\图片浏览器\1.2.0.1\001.dll
    /// +------------------------------\002.dll
    /// +------------------------------\003.dll
    /// +------------------------------\Imgviewer.exe
    /// 该文件夹内不应当包含更新时拷贝文件的程序，因为在拷贝的时候“拷贝程序.exe”是处于运行状态的，此时无法拷贝“拷贝程序.exe”本身；
    /// 程序更新的流程如下，以“图片浏览器”为例：
    /// 1.客户端调用/Api/CheckUpdate接口校验是否有新版本，传入参数为appname,version，服务端从e:\\upgrades\图片浏览器\文件夹内获取代表版本号
    ///   的文件夹：1.2.0.1，并将其返回给客户端，客户端用本身的版本号和服务端返回的版本号进行比较，如果两者一直，说明如果没有更新，中断后续
    ///   步骤，如果不一致，表示有更新的版本，执行第二步。
    /// 2.客户端调用/Api/GetAllFiles接口请求新版本的程序的所有文件。
    /// 3.通过FileTransfer逐个下载所有文件到临时文件夹
    /// 4.客户端打开“拷贝程序”，并关闭本身，拷贝程序等待2秒，确保Imgviewer.exe已经关闭。
    /// 5.拷贝程序从临时文件夹内将文件拷贝出来，替换掉所有当前的旧版的程序文件，拷贝完成后运行Imgview.exe并关闭拷贝程序本身
    /// 6.更新完成
    /// </summary>
    public class UniversalUpgradeUtils
    {
        private string _basePath;
        private WSocketListenerHelper _wsockHelper;
        private FileTransfer _transferHelper;

        /// <summary>
        /// 通用远程升级服务端，基于WSocketListenerHelper和FileTransfer
        /// </summary>
        /// <param name="wsockHelper"></param>
        /// <param name="transferHelper"></param>
        public UniversalUpgradeUtils(string basePath, WSocketListenerHelper wsockHelper, FileTransfer transferHelper)
        {
            try
            {
                if (wsockHelper == null)
                    throw new Exception("传入wsockHelper为空~");
                if (transferHelper == null)
                    throw new Exception("传入transferHelper为空~");

                this._basePath = basePath;
                this._wsockHelper = wsockHelper;
                this._transferHelper = transferHelper;

                if (Directory.Exists(_basePath) == false)
                    Directory.CreateDirectory(_basePath);

                _basePath = FileIO.FormatPath(_basePath, true);

                _wsockHelper.SubscribeTopic("/Api/CheckUpdate", ApiCheckUpdate);                    //检查更新
                _wsockHelper.SubscribeTopic("/Api/GetAllFiles", ApiGetAllFiles);                    //获取所有更新文件
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 检查更新，传入参数：
        /// AppName:程序名称
        /// Ver:当前版本
        /// </summary>
        /// <param name="info"></param>
        /// <param name="connection"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        private object ApiCheckUpdate(WSMessageInfo info, IWebSocketConnection connection)
        {
            try
            {
                string newestVer = string.Empty;

                CheckParam(info);

                JsonDocument doc = JsonDocument.Parse(info.val);
                JsonElement ele = doc.RootElement;
                JsonObject obj = JsonObject.Create(ele);

                string appName = obj["appname"].ToString();
                string ver = obj["version"].ToString();

                string appPath = $"{_basePath}{appName}";
                DirectoryInfo diApp = new DirectoryInfo(appPath);
                if (diApp.Exists == false)
                    diApp.Create();

                DirectoryInfo[] dirVers = diApp.GetDirectories();

                //如果一个版本的文件夹都没有
                if (dirVers.IsNullOrEmpty())
                    newestVer = ver;
                else if (dirVers[0].Name != ver)
                    newestVer = dirVers[0].Name;

                return new
                {
                    Version = newestVer
                };
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 获取所有更新文件
        /// </summary>
        /// <param name="info"></param>
        /// <param name="connection"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        private object ApiGetAllFiles(WSMessageInfo info, IWebSocketConnection connection)
        {
            try
            {
                CheckParam(info);

                JsonDocument doc = JsonDocument.Parse(info.val);
                JsonElement ele = doc.RootElement;
                JsonObject obj = JsonObject.Create(ele);

                List<TransferFile> dfiles = new List<TransferFile>();

                string appName = obj["appname"].ToString();
                string ver = obj["version"].ToString();

                string verPath = $"{_basePath}{appName}/{ver}";
                DirectoryInfo diVer = new DirectoryInfo(verPath);

                //递归获取所有文件
                List<TransferFile> subFiles = FileTransfer.RecursionFiles(diVer, verPath);
                if (subFiles.IsNullOrEmpty() == false)
                    dfiles.AddRange(subFiles);

                //创建传输任务
                string transferToken = Guid.NewGuid().ToString();
                
                if (dfiles.Count > 0)
                {
                    TransferTask task = new TransferTask(transferToken, dfiles);
                    _transferHelper.AddTransferTask(task);
                }

                return new
                {
                    TransferToken = transferToken,
                    Files = dfiles
                };
            }
            catch (Exception)
            {
                throw;
            }
        }

        private static void CheckParam(WSMessageInfo message)
        {
            try
            {
                if (message == null)
                    throw new Exception("传入参数为空~");
                if (string.IsNullOrWhiteSpace(message.val))
                    throw new Exception("传入参数为空~");
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}
